﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

using TinyEngine.Core;
using TinyEngine.Core.Entities;
using TinyEngine.Core.Services;
using Microsoft.Xna.Framework.Graphics;
using WinFormsGraphicsDevice;
using Microsoft.Xna.Framework.Content;
using TinyEngine.Core.Scene;
using System.Windows.Controls;
using System.Xml.Serialization;
using TinyEngine.Core.Asset;
using Microsoft.Xna.Framework;
using System.Reflection;
using System.Diagnostics;

namespace TinyEngine.Editor.Extensibility
{
    public class EditorSession
    {
        private static Dictionary<Type, Type> editorTypes = new Dictionary<Type, Type>();
        public GraphicsDevice GlobalGraphicsDevice { get; set; }
        public IServiceProvider Services { get; set; }
        public Menu MainMenu { get; set; }
        public static CachedSession cached = new CachedSession();

        [XmlIgnore]
        public string RootPath { get; set; }

        // Modified for http://www.yoda.arachsys.com/csharp/singleton.html #4. (Jon Skeet is a code machine)
        private static readonly EditorSession defaultInstance = new EditorSession();

        public static EditorSession Instance
        {
            get
            {
                return defaultInstance;
            }
        }

        static EditorSession()
        {
        }

        public EditorSession()
        {
            this.SelectedItems = new List<IEntity>();
        }

        public event Action ProjectLoaded;
        public event Action<TinyScene> SceneLoaded;
        public event Action SelectedItemsCleared;
        public event Action<IEntity> ItemSelected;
        public event Action AssetsAdded;

        private void FireItemSelected(IEntity entity)
        {
            if (ItemSelected != null)
                ItemSelected(entity);
        }

        private void FireProjectLoaded()
        {
            if (ProjectLoaded != null)
                ProjectLoaded();
        }

        private void FireSceneLoaded(TinyScene scene)
        {
            if (SceneLoaded != null)
                SceneLoaded(scene);
        }

        public static void Init(GraphicsDevice device, IServiceProvider services, Action complete)
        {
            Instance.GlobalGraphicsDevice = device;
            Instance.Services = services;
            if (File.Exists("cached"))
            {
                cached.Load();

                Instance.ProjectFileLocation = cached.ProjectPath;
                Instance.SceneFileLocation = cached.ScenePath;

                if (!string.IsNullOrEmpty(Instance.ProjectFileLocation))
                {
                    Instance.LoadProject();

                    if (!string.IsNullOrEmpty(Instance.SceneFileLocation))
                    {
                        Instance.LoadScene(Instance.SceneFileLocation);
                    }
                }
            }
            complete();
        }
        
        private void ResetContentBuilder()
        {
            if (contentBuilder == null)
            {
                contentBuilder = new ContentBuilder();
            }

            contentBuilder.Clear();
        }

        private void AddTextureToContent(string name, string loc)
        {
            contentBuilder.Add(loc, name, "TextureImporter", "TextureProcessor");
        }
        
        public void LoadScene(string filename)
        {
            Trace.WriteLine("Loading scene " + filename);

            ResetContentBuilder();

            SceneManager sceneM = ServiceManager.GetService<SceneManager>();
            Trace.WriteLine("Registering Scene Manager");

            sceneM.Register();
            
            sceneM.LoadForEditor(filename, sceneM.ActiveGameScene);

            var assets = ServiceManager.GetService<AssetLibrary>();
            foreach (var item in assets.Sprites)
            {
                if (!string.IsNullOrEmpty(item.Value.Location))
                {
                    var fullPath = Path.Combine(ActiveProject.RootPath, ActiveProject.ContentPath, item.Value.Location);
                    var name = item.Value.Location.Substring(0, item.Value.Location.LastIndexOf("."));
                    AddTextureToContent(name, fullPath);
                }
            }

            contentBuilder.Build();

            foreach (var item in assets.Sprites)
            {
                item.Value.Load();
            }

            sceneManager = sceneM;

            foreach (var item in sceneM.ActiveGameScene.Graph.EveryItem())
            {
                item.PositionChanged();
            }
            
            cached.ScenePath = filename;

            HasLoadedScene = true;
            FireSceneLoaded(sceneM.ActiveGameScene);

            EditorSession.RequestSceneRefresh();
        }
        public SceneManager sceneManager = null;
        private ContentBuilder contentBuilder = null;
        public TinyProject ActiveProject { get; set; }
        
        private void LoadProject()
        {
            LoadProject(ProjectFileLocation);            
        }
        
        private string GetContentRoot()
        {
            return Path.GetFullPath(Path.Combine(ActiveProject.RootPath, ActiveProject.ContentPath));
        }

        public bool HasLoadedProject { get; set; }
        public bool HasLoadedScene { get; set; }

        public string ProjectFileLocation { get; set; }

        public string SceneFileLocation { get; set; }

        public List<IEntity> SelectedItems { get; private set; }
        
        public void ClearSelectedItems()
        {
            this.SelectedItems.Clear();
            FireSelectedItemsCleared();
        }

        private void FireSelectedItemsCleared()
        {
            if (SelectedItemsCleared != null)
                SelectedItemsCleared();
        }

        public void SetSelectedItem(IEntity entity)
        {
            ClearSelectedItems();
            this.SelectedItems.Add(entity);
            FireItemSelected(entity);
        }


        public void LoadProject(string filename)
        {
            var proj = TinyProject.Load(filename);
            ActiveProject = proj;
            RootPath = Path.GetDirectoryName(filename);
            var asm = new AssemblyService();
            asm.RegisterAssembly(typeof(IEntity).Assembly);
            asm.RegisterAssembly(typeof(SpriteEntity).Assembly);

            LoadAllProjectsAssemblies(asm, Path.Combine(RootPath, "bin\\x86\\Debug\\"));

            ServiceManager.RegisterService(asm);

            ServiceManager.RegisterService(TinyGame.InitGameEngine(GlobalGraphicsDevice, null));
            
            TinyGame.SetupDefaultServices();


            ServiceManager.GetService<AssetLibrary>().GetContentRoot = GetContentRoot;

            contentBuilder = new ContentBuilder();
            var content=  new ContentManager(Services, contentBuilder.OutputDirectory);
            
            ActiveProject.Content = content;
            ServiceManager.RegisterService(new ContentManagerService(content));

            ServiceManager.RegisterService(new RenderTargetService());

            cached.ProjectPath = filename;
            HasLoadedProject = true;
            Instance.FireProjectLoaded();
        }

        private void LoadAllProjectsAssemblies(AssemblyService assemblyService, string directoryPath)
        {
            string searchPattern = "*.dll|*.exe";
            string[] searchPatterns = searchPattern.Split('|');

            foreach (string pattern in searchPatterns)
            {
                Directory.GetFiles(directoryPath, pattern, SearchOption.AllDirectories).Select(f => new FileInfo(f)).ToList().ForEach(item =>
                {
                    var assembly = Assembly.LoadFrom(item.FullName);
                    assemblyService.RegisterAssembly(assembly);
                });
            }
        }

        public static void SaveAll()
        {
            EditorSession.Save();
            Instance.ActiveProject.Save(cached.ProjectPath);
            Instance.SaveScene();
            Instance.SaveAssetLibrary();
            
        }

        private void SaveAssetLibrary()
        {
            
            if(sceneManager == null || sceneManager.ActiveGameScene == null)
                return;

            var assets=ServiceManager.GetService<AssetLibrary>();
            if (!string.IsNullOrEmpty(sceneManager.ActiveGameScene.AssetsPath))
            {
                var content = Path.GetFullPath(Path.Combine(ActiveProject.RootPath, ActiveProject.ContentPath));
                var fullPath = Path.Combine(content, sceneManager.ActiveGameScene.AssetsPath);

                SaveAssetFile(assets, fullPath);
            }
        }

        private void SaveAssetFile(AssetLibrary assets, string fullPath)
        {
            assets.Save(fullPath);
        }

        private void SaveScene()
        {
            if (!string.IsNullOrEmpty(cached.ScenePath))
            {
                sceneManager.Save(sceneManager.ActiveGameScene, cached.ScenePath);
            }
        }

        public static void Save()
        {
            cached.Save();
        }

        public void FireAssetsAdded()
        {
            var ass = ServiceManager.GetService<AssetLibrary>();
            var list = new List<SpriteAsset>();

            foreach (var item in ass.Sprites.Values)
            {
                if (item.Asset == null)
                {
                    if (!string.IsNullOrEmpty(item.Location))
                    {
                        var path = Path.GetFullPath(Path.Combine(ActiveProject.RootPath, ActiveProject.ContentPath));
                        path = Path.Combine(path, item.Location);
                        var shortPath = item.Location;
                        var contentName = item.Location.Substring(0, item.Location.LastIndexOf("."));
                        AddTextureToContent(contentName, path);
                        list.Add(item);           
                    }
                }
            }

            contentBuilder.Build();

            foreach (var item in list)
            {
                item.Load();
            }
            

            if (AssetsAdded != null)
                AssetsAdded();
        }

        public static Action<UserControl> LaunchDialogRequest;
        public static void LaunchDialogWindow(UserControl control)
        {
            LaunchDialogRequest(control);
        }

        public static event Action SceneRefreshRequested;
        public static void RequestSceneRefresh()
        {
            if (SceneRefreshRequested != null)
                SceneRefreshRequested();
        }

        public event Action<Vector3> MoveSceneRequest;
        public void MoveScene(Vector3 vector3)
        {
            if(MoveSceneRequest!=null)
                MoveSceneRequest(vector3);
        }

        public event Action SceneClosed;
        public event Action ProjectClosed;

        public static void LoadEditor(IEntity iEntity)
        {
            var type = iEntity.GetType();
            
            if (!editorTypes.ContainsKey(type))
                return;

            var item = editorTypes[type];
            var instance = (IEntityEditor)Activator.CreateInstance(item);
            instance.SetEntity(iEntity);

            EditorSession.LaunchDialogWindow(instance as UserControl);
        }

        public static void RegisterTypeEditor(Type entityType, Type editorType)
        {
            editorTypes.Add(entityType, editorType);
        }

        public void CloseScene()
        {
            ResetContentBuilder();
            cached.ScenePath = string.Empty;

            this.HasLoadedScene = false;
            this.SceneFileLocation = string.Empty;
            ServiceManager.GetService<AssetLibrary>().Unload();


            this.sceneManager = null;


            if (SceneClosed != null)
                SceneClosed();

        }

        public void CloseAll()
        {
            CloseScene();

            cached.ProjectPath = string.Empty;
            this.ActiveProject = null;
            this.HasLoadedProject = false;

            this.ProjectFileLocation = string.Empty;

            if (ProjectClosed != null)
                ProjectClosed();
        }

    }
}
